BlockingQueue是什么
- BlockingQueue是一个阻塞队列的接口
- BlockingQueue是线程安全的
- BlockingQueue具有先进先出的特点
- 当队列满的时候进行入队操作会阻塞,当队列空的时候进行出队操作会阻塞
BlockingQueue提供的接口
BlockingQueue提供的接口有四种不同的方法,具体如下表所示
操作 | Throws Exception | Special Value | Blocks | Times Out |
---|---|---|---|---|
插入 | add(o) | offer(o) | put(o) | offer(o, timeout, timeunit) |
删除 | remove(o) | poll() | take() | poll(timeout, timeunit) |
查询 | element() | peek() | - | - |
这四种不同的方法对应的特点分别是
- ThrowsException:如果操作不能马上进行,则抛出异常
- SpecialValue:如果操作不能马上进行,将会返回一个特殊的值,一般是true或者false
- Blocks:如果操作不能马上进行,操作会被阻塞
- TimesOut:如果操作不能马上进行,操作会被阻塞指定的时间,如果指定时间没执行,则返回一个特殊值
LinkedBlockingQueue是什么
- LinkedBlockingQueue是一个基于链表的阻塞队列
- LinkedBlockingQueue可以指定容量大小,默认为Integer.MAX_VALUE
- LinkedBlockingQueue是线程安全的
- LinkedBlockingQueue具有先进先出的特点
- 当队列满的时候进行入队(put)操作会阻塞,当队列空的时候进行出队(take)操作会阻塞
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable
LinkedBlockingQueue成员变量
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
private final int capacity; //容量边界
private final AtomicInteger count = new AtomicInteger(); //当前元素的个数
transient Node<E> head; //队列头指针
private transient Node<E> last; //队列尾指针
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();//容量不为满
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();//容量不为空
由于LinkedBlockingQueue是基于链表实现的阻塞队列,因此需要个结构体Node来存储链表的数据。其中两个锁是为了保证线程安全的,而两个Condition则是用来阻塞队列的。两个锁的用意是
- ArrayBlockingQueue只有lock锁,说明入队操作和出队操作不能同时进行
- LinkedBlockingQueue有put锁和take锁两把锁,说明入队操作和出队操作可以同时进行
LinkedBlockingQueue构造方法
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
构造函数
- 创造一个容量为Integer.MAX_VALUE的队列
- 创造一个指定容量大小的队列,如果参数小于等于零,则抛异常
LinkedBlockingQueue的存储
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);//根据当前的值构造一个节点
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;//提供原子操作的Integer类型
putLock.lockInterruptibly();//获取锁
try {
while (count.get() == capacity) {
notFull.await();//容量已满,需要等待
}
enqueue(node);//链表的插入操作
c = count.getAndIncrement();//count自增,返回原来的值
if (c + 1 < capacity)
notFull.signal();//容量未满,唤醒等待
} finally {
putLock.unlock();//释放锁
}
if (c == 0)
signalNotEmpty();//原来的容量是空的,唤醒等待
}
private void enqueue(Node<E> node) {
last = last.next = node;//加入链尾
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
存储的操作很有逻辑
- 存储前,先判断容量是否为满,如果满了就等待
take()
的通知,不满就执行链表的插入操作 - 插入后,需要验证插入后是否已经满了,如果没满就应该通知
- 最后,如果原来的容量是空的,那么在插入后,自然就不是空的,需要去唤醒不为空的通知
LinkedBlockingQueue的获取
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();//获取锁
try {
while (count.get() == 0) {
notEmpty.await();//容量已空,需要等待
}
x = dequeue();//链表的删除操作
c = count.getAndDecrement();//count自减,返回原来的值
if (c > 1)
notEmpty.signal();//容量不为空,唤醒等待
} finally {
takeLock.unlock();//释放锁
}
if (c == capacity)
signalNotFull();//原来的容量是满的,唤醒等待
return x;
}
private E dequeue() {
Node<E> h = head;
Node<E> first = h.next;
h.next = h;
head = first;
E x = first.item;
first.item = null;
return x;
}
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
获取的操作也很有逻辑
- 获取前,先判断容量是否为空,如果空了就等待
put()
的通知,不空就执行链表的删除操作 - 删除后,需要验证删除后是否已经空了,如果没空就应该通知
- 最后,如果原来的容量是满的,那么在删除后,自然就不是满的,需要去唤醒不为满的通知
总结
- LinkedBlockingQueue队列是基于链表和Condition类来实现的
- LinkedBlockingQueue的存储和获取采用生产消费模式
- LinkedBlockingQueue的采用两把锁进行了读写分离,有利于提高并发度
- LinkedBlockingQueue的队列中不允许元素为null
- LinkedBlockingQueue队列性能好于ArrayBlockingQueue
- LinkedBlockingQueue队列在Executors.newFixedThreadPool()被使用